function res=h5dataput(h5file,varname,data,varargin)
% H5DATAPUT:  writes an HDF5 dataset
%
%
% H5DATAPUT(HDFFILE,VARNAME,DATA,parameter,value,...) creates variable to the variable given
% by VARNAME to path given by PATHNAME.
%
%   parameter
%     'compact'  -boolean to specify whether to use a compact dataset if
%     the data is small enough
%     'chunk_size'  - size of the chunks of the dataset
%     'compression' - 0-9  compression level
%
%     'max_size'    - the maximum size of the dataset to create
%
% NOTES:   H5dataput will create a dataset to accomodate the matlab
% variable given. If the dataset exists and the new dataset is a match it
% overwrites the previous data.  If it doesn't match the previous size then
% the dataset is removed and replaced by the new dataset.  HDF5 files do
% not handle the space freed by old datasets particularly well.  In this
% case the space freed is used by the new data however space not used by
% the new dataset is not removed from the file,  meaning the filesize
% cannot get smaller.  If this is repeated numerous times it could result
% in a significant amount of zombie space in the file.
%
%if the procedure was unsuccessful res will be -1.
%
res=-1;

p = inputParser;
p.addParamValue('chunk_size',0,@isnumeric);
p.addParamValue('max_size',0,@isnumeric);
p.addParamValue('compression',0,@isnumeric);
p.addParamValue('compact',1,@(x)isnumeric(x)||islogical(x));
p.parse(varargin{:});
params = p.Results;
params = validate_params(h5file,varname,data,params);

flags = 'H5F_ACC_RDWR';
plist_id = 'H5P_DEFAULT';

if ischar(h5file)
    file_id = H5F.open ( h5file, flags, plist_id );
else
    file_id=h5file;
end


params.dataspace_id                     = set_dataspace_id (data ,params);
params.datatype_id                      = set_datatype_id ( data);
%grabbing the size of the dataset for consideration of compact dataset
s=whos('data');
params.bytes=s.bytes;
params                          = set_property_list(params);
data_id                         = set_data_id ( file_id, varname,params );

try   %We are going to try to write the dataset
    if ((isreal(data))||(isstruct(data))||iscell(data))
        H5D.write(data_id,'H5ML_DEFAULT','H5S_ALL','H5S_ALL','H5P_DEFAULT', data);
    else
        dstr.real=real(data);
        dstr.imag=imag(data);
        H5D.write(data_id,'H5ML_DEFAULT','H5S_ALL','H5S_ALL','H5P_DEFAULT', dstr);
    end
    res=0;
catch ME  %this may fail for any number of reasons.  the most common is that that
    %dataset already exists and does not match the new one.  Therefore we
    %should erase the old one and replace it
    H5D.close(data_id);
    H5G.unlink(file_id,varname);
    %recreate the data set again
    data_id = set_data_id (file_id, varname,params);
    try
        if ((isreal(data))||(isstruct(data)))
            H5D.write(data_id,'H5ML_DEFAULT','H5S_ALL','H5S_ALL','H5P_DEFAULT', data);
        else
            dstr.real=real(data);
            dstr.imag=imag(data);
            H5D.write(data_id,'H5ML_DEFAULT','H5S_ALL','H5S_ALL','H5P_DEFAULT', dstr);
        end
        res=0;
    catch ME2
        res=-1;
    end
end

H5D.close(data_id);
H5T.close(params.datatype_id);
H5S.close(params.dataspace_id);



if ischar(h5file)
    H5F.close(file_id);
end
end


%===============================================================================
% SET_Property_list
%
% Setup the dataet ID.  We need to check as to whether or not the dataset
% already exists.
function params = set_property_list(params)
params.dcpl = H5P.create('H5P_DATASET_CREATE'); % create property list
if (params.compression>0)
    params.compact=false;
end
if (params.chunk_size)
    H5P.set_chunk(params.dcpl, fliplr(params.chunk_size)); % set chunk size in property list
end
if (params.compression>0)
    H5P.set_deflate(params.dcpl,params.compression);
    
end
%check for the possibility of a compact dataset
% which is faster for datasets that are less than ~30kb
if (params.compact)
    if (params.bytes<28000)
        H5P.set_layout(params.dcpl,'H5D_COMPACT');
    end
    
end

end


%===============================================================================
% SET_DATASET_ID
%
% Setup the dataet ID.  We need to check as to whether or not the dataset
% already exists.
function datasetID = set_data_id ( parent_id, dataname, params)

switch(version('-release'))
    case { '2007a', '2007b', '2008a', '2008b' }
        datasetID = H5D.create(parent_id,dataname,params.datatype_id, ...
            params.dataspace_id,'H5P_DEFAULT');

    otherwise
        % create any intermediate groups along the way.
        params.lcpl = H5P.create('H5P_LINK_CREATE');
        H5P.set_create_intermediate_group(params.lcpl,1);
        dapl = 'H5P_DEFAULT';
    
    
        % need to use the 1.8.x version of H5D.create for this.
        datasetID = H5D.create(parent_id,dataname,...
            params.datatype_id, params.dataspace_id, ...
            params.lcpl,params.dcpl,dapl);
    
end

end

%===============================================================================
% SET_DATASPACE_ID
%
% Setup the dataspace ID.  This just depends on how many elements the
% attribute actually has.
function dataspace_id = set_dataspace_id ( datavalue,params )
if ischar(datavalue)
    dataspace_id = H5S.create('H5S_SCALAR');
else
    if (params.size(1) ~= numel(datavalue))
        rank = ndims(datavalue);
        dims = fliplr(params.size);
        max_dims=fliplr(params.max_size);
    else
        if (((isequal(params.chunk_size,0))&&(prod(params.max_size(2:end))==1))||(numel(params.max_size)==1))
            rank = 1;
            dims = params.size(1);
            max_dims=params.max_size(1);
        else
            rank = ndims(datavalue);
            dims = fliplr(params.size);
            max_dims=fliplr(params.max_size);
        end
    end
    dataspace_id=H5S.create('H5S_SIMPLE');
    H5S.set_extent_simple(dataspace_id,rank,...
        dims,max_dims);
end

end


%===============================================================================
% SET_DATATYPE_ID
%
% We need to choose an appropriate HDF5 datatype based upon the attribute
% data.
function datatype_id = set_datatype_id ( datavalue )
switch class(datavalue)
    case 'double'
        if (isreal(datavalue))
            datatype_id = H5T.copy('H5T_NATIVE_DOUBLE');
        else
            datatype_id=H5T.create('H5T_COMPOUND',16);
            H5T.insert(datatype_id,'real',0,'H5T_NATIVE_DOUBLE');
            H5T.insert(datatype_id,'imag',8,'H5T_NATIVE_DOUBLE');
        end
    case 'single'
        if (isreal(datavalue))
            datatype_id = H5T.copy('H5T_NATIVE_FLOAT');
        else
            datatype_id=H5T.create('H5T_COMPOUND',8);
            H5T.insert(datatype_id,'real',0,'H5T_NATIVE_FLOAT');
            H5T.insert(datatype_id,'imag',4,'H5T_NATIVE_FLOAT');
        end
    case 'int64'
        datatype_id = H5T.copy('H5T_NATIVE_LLONG');
    case 'uint64'
        datatype_id = H5T.copy('H5T_NATIVE_ULLONG');
    case 'int32'
        datatype_id = H5T.copy('H5T_NATIVE_INT');
    case 'uint32'
        datatype_id = H5T.copy('H5T_NATIVE_UINT');
    case 'int16'
        datatype_id = H5T.copy('H5T_NATIVE_SHORT');
    case 'uint16'
        datatype_id = H5T.copy('H5T_NATIVE_USHORT');
    case 'int8'
        datatype_id = H5T.copy('H5T_NATIVE_SCHAR');
    case 'uint8'
        datatype_id = H5T.copy('H5T_NATIVE_UCHAR');
    case 'char'
        datatype_id = H5T.copy('H5T_C_S1');
        if any(size(datavalue) == 1)
            H5T.set_size(datatype_id,numel(datavalue));
        elseif (isempty(datavalue))
             H5T.set_size(datatype_id,'H5T_VARIABLE');
        else
            H5T.set_size(datatype_id,size(datavalue,1));
        end
        H5T.set_strpad(datatype_id,'H5T_STR_NULLTERM');
    case 'cell'
        if iscellstr(datavalue)
            datatype_id = H5T.copy('H5T_C_S1');
            H5T.set_size(datatype_id,'H5T_VARIABLE');
            H5T.set_strpad(datatype_id,'H5T_STR_NULLTERM');
        else
            if (length(unique(cellfun(@class,datavalue,'UniformOutput',false)))==1)
                base_id= set_datatype_id ( datavalue{1} );
                datatype_id=H5T.vlen_create(base_id);
            else
                error('MATLAB:H5DATAPUT:unsupportedDatatype', ...
                    'cell arrays must be of a single class' );
            end
        end
    case 'struct'
        
        
        datatype_id=struct_to_h5type(datavalue);
        
    otherwise
        error('MATLAB:H5DATAPUT:unsupportedDatatype', ...
            '''%s'' is not a supported H5DATAPUT datatype.\n', class(datavalue) );
end
end
%--------------------------------------------------------------------------
function params = validate_params(hfile,varname,data,params)

if ~ischar(hfile)
    if ~isequal(class(hfile),'H5ML.id')
        error('hdf5tools:H%DATACREATE:badDatatype', ...
            'Filename input argument must have datatype char.' );
        
    end
end

if ~ischar(varname)
    error('hdf5tools:H5DATACREATE:badDatatype', ...
        'VARNAME input argument must have datatype char.' );
end

% if (isstruct(data))
%     [valid,sz]=test_compound_struct(data);
%     if (valid)
%         params.size=sz;
%     else
%         error('MATLAB:H5DATAPUT:invalid_struct',...
%             'Structure is not valid for direct mapping to datatype,  use h5structput to write general structure\n');
%     end
% else
%     params.size=size(data);
% end
params.size=size(data);
if (isequal(params.max_size,0))
    params.max_size=params.size;
else
    if (numel(params.max_size)~=numel(params.size))
        if (length(params.max_size)==1)
            if ((length(params.size)==2)&&(params.size(2)==1))
                params.size=params.size(1);
            else
                error('dataset size and max size have different dimensions');
            end
        else
        error('dataset size and max size have different dimensions');
        end
    else
        if (~isequal(params.max_size,params.size))
            params.compact=0;
            if (isequal(params.chunk_size,0))
                params.chunk_size=params.size;
            end
            
        end
        md=size(data);
        for kk=1:length(md)
            if (md(kk)>params.max_size(kk))
                error('max size element %d set smaller than data size',kk);
            end
        end
    end
end

if (~isequal(params.chunk_size,0))
    if (numel(params.chunk_size)~=numel(params.size))
        error('dataset size and chunk size have different dimensions');
    end
end

if ((params.compression<0)||(params.compression>9))
    error('invalid compression level');
    
else
    if (params.compression>0)
        if (isequal(params.chunk_size,0))
            params.chunk_size=params.size;
        end
    end
end
return
end
